﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PerCederberg.Grammatica.Runtime;

namespace SafeScript.MetaModel
{
    public class MemberVariable : INamed
    {
        public string Name { get; set; }
        public ITypeDef Type;
        public Statements.Operator InitialValue;
        public Namespace Owner;
        public bool IsStatic;
        /// <summary>
        /// May be null.
        /// </summary>
        public MemberVariable Template;

        private bool marker = false;

        /// <summary>
        /// May be null.
        /// </summary>
        public MemberAttribute[] Attributes { get; set; }

        public T GetAttribute<T>() where T : MemberAttribute
        {
            if (this.Attributes == null)
                return default(T);
            foreach (MemberAttribute attr in this.Attributes)
            {
                if (attr is T)
                    return attr as T;
            }
            return default(T);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="report"></param>
        /// <returns>Null if the type could not be determined</returns>
        public virtual ITypeDef ComputeType(ErrorReport report)
        {
            if (this.InitialValue == null && this.Type == null)
            {
                report.Add(new ErrorEntry(this.Node) { Info = this.Name, Type = ErrorType.UnresolvedType });
                return null;
            }

            if (this.InitialValue == null && this.Type != null)
                return this.Type;

            // Recursive dependency when infering the type?
            if ( this.marker )
            {
                this.marker = false;
                report.Add(new ErrorEntry(this.Node) { Info = this.Name, Type = ErrorType.UnresolvedType });
                return null;
            }

            this.marker = true;
            ITypeDef t = this.InitialValue.ComputeType(this.Type, false, report);
            this.marker = false;
            // Some error?
            if (t == null)
                return null;

            if (this.Type == null)
            {
                this.Type = t;
                return t;
            }

            if (!t.CanBeAssignedTo(this.Type))
            {
                this.ReportIncompatibleTypes(this.Type, t, report);
                return null;
            }

            // Check that the type of the variable and the initialization expression match
            // TODO
            return this.Type;
        }

        public virtual MemberVariable Instantiate(Dictionary<TemplateParameterType, ITypeDef> templateArguments)
        {
            MemberVariable v = new MemberVariable() { Name = this.Name, IsStatic = this.IsStatic, Template = this, Attributes = this.Attributes };
            v.Type = this.Type.Instantiate(templateArguments);
            return v;
        }

        private void ReportIncompatibleTypes(ITypeDef type1, ITypeDef type2, ErrorReport report)
        {
            report.Add(new ErrorEntry(this.Node) { Type = ErrorType.IncompatibleTypes });
        }

        internal Node Node;
        internal Using Using;
    }
}
